#include "mmdmodel.h"

Model::Model(QString modelFile,QString MMDPath,MMDScene * scene):
    m_posVBO(QOpenGLBuffer::VertexBuffer),
    m_norVBO(QOpenGLBuffer::VertexBuffer),
    m_uvVBO(QOpenGLBuffer::VertexBuffer),
    m_ibo(QOpenGLBuffer::IndexBuffer)
{
    initializeOpenGLFunctions();

    this->scene = scene;
    QOpenGLShaderProgram & program = scene->shader->get();
    program.bind();

    glGenTextures(1, &m_dummyColorTex);
    glBindTexture(GL_TEXTURE_2D, m_dummyColorTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glBindTexture(GL_TEXTURE_2D, 0);

    glGenTextures(1, &m_dummyShadowDepthTex);
    glBindTexture(GL_TEXTURE_2D, m_dummyShadowDepthTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
    glBindTexture(GL_TEXTURE_2D, 0);

    model = std::shared_ptr<saba::PMXModel>(new saba::PMXModel);

    model->Load(modelFile.toStdString(),MMDPath.toStdString());
    model->InitializeAnimation();

    m_mmdVAO.create();
    m_mmdVAO.bind();

    // Setup vertices
    size_t vtxCount = model->GetVertexCount();

    m_posVBO.create();
    m_posVBO.bind();
    m_posVBO.setUsagePattern(QOpenGLBuffer::UsagePattern::DynamicDraw);
    m_posVBO.allocate(sizeof(glm::vec3) * vtxCount);
    program.enableAttributeArray(scene->shader->m_inPos);
    program.setAttributeBuffer(scene->shader->m_inPos,GL_FLOAT,0,3,sizeof(glm::vec3));
    m_posVBO.release();

    m_norVBO.create();
    m_norVBO.bind();
    m_norVBO.setUsagePattern(QOpenGLBuffer::UsagePattern::DynamicDraw);
    m_norVBO.allocate(sizeof(glm::vec3) * vtxCount);
    program.enableAttributeArray(scene->shader->m_inNor);
    program.setAttributeBuffer(scene->shader->m_inNor,GL_FLOAT,0,3,sizeof(glm::vec3));
    m_norVBO.release();

    m_uvVBO.create();
    m_uvVBO.bind();
    m_uvVBO.setUsagePattern(QOpenGLBuffer::UsagePattern::DynamicDraw);
    m_uvVBO.allocate(sizeof(glm::vec2) * vtxCount);
    program.enableAttributeArray(scene->shader->m_inUV);
    program.setAttributeBuffer(scene->shader->m_inUV,GL_FLOAT,0,2,sizeof(glm::vec2));
    m_uvVBO.release();

    size_t idxSize = model->GetIndexElementSize();
    size_t idxCount = model->GetIndexCount();
    m_ibo.create();
    m_ibo.bind();
    m_ibo.setUsagePattern(QOpenGLBuffer::UsagePattern::StaticDraw);
    m_ibo.allocate(model->GetIndices(),idxSize * idxCount);

    m_mmdVAO.release();
    program.release();

    if (idxSize == 1)
    {
        m_indexType = GL_UNSIGNED_BYTE;
    }
    else if (idxSize == 2)
    {
        m_indexType = GL_UNSIGNED_SHORT;
    }
    else if (idxSize == 4)
    {
        m_indexType = GL_UNSIGNED_INT;
    }

    for (size_t i = 0; i < model->GetMaterialCount(); i++)
    {
        auto mmdMat = model->GetMaterials()[i];
        Material mat(mmdMat);
        if (!mmdMat.m_texture.empty())
        {
            mat.m_texture = scene->GetTexture(QString::fromStdString(mmdMat.m_texture));
        }
        if (!mmdMat.m_spTexture.empty())
        {
            mat.m_spTexture = scene->GetTexture(QString::fromStdString(mmdMat.m_spTexture));
        }
        if (!mmdMat.m_toonTexture.empty())
        {
            mat.m_toonTexture = scene->GetTexture(QString::fromStdString(mmdMat.m_toonTexture));
        }
        m_materials.push_back(mat);
    }
    anim = new saba::VMDAnimation;
    anim->Create(model);
    InitMorph();
}

Model::~Model()
{
    delete anim;
}

void Model::Draw(){
    const auto& view = scene->m_viewMat;
    const auto& proj = scene->m_projMat;

    auto world = glm::mat4(1.0f);
    auto wv = view * world;
    auto wvp = proj * view * world;
    auto wvit = glm::mat3(view * world);
    wvit = glm::inverse(wvit);
    wvit = glm::transpose(wvit);

    glActiveTexture(GL_TEXTURE0 + 3);
    glBindTexture(GL_TEXTURE_2D, m_dummyShadowDepthTex);
    glActiveTexture(GL_TEXTURE0 + 4);
    glBindTexture(GL_TEXTURE_2D, m_dummyShadowDepthTex);
    glActiveTexture(GL_TEXTURE0 + 5);
    glBindTexture(GL_TEXTURE_2D, m_dummyShadowDepthTex);
    glActiveTexture(GL_TEXTURE0 + 6);
    glBindTexture(GL_TEXTURE_2D, m_dummyShadowDepthTex);

    glEnable(GL_DEPTH_TEST);

    // Draw model
    size_t subMeshCount = model->GetSubMeshCount();
    for (size_t i = 0; i < subMeshCount; i++)
    {
        const auto& subMesh = model->GetSubMeshes()[i];
        const auto& mat = m_materials[subMesh.m_materialID];
        const auto& mmdMat = mat.m_mmdMat;
        QOpenGLShaderProgram & program = scene->shader->get();

        if (mat.m_mmdMat.m_alpha == 0)continue;

        program.bind();
        m_mmdVAO.bind();

        GLfloat value[4][4];
        memcpy(&value,&wv[0][0],sizeof(value));
        program.setUniformValue(scene->shader->m_uWV,value);
        memcpy(&value,&wvp[0][0],sizeof(value));
        program.setUniformValue(scene->shader->m_uWVP,value);

        bool alphaBlend = true;
        program.setUniformValue(scene->shader->m_uAmbinet,QVector3D(mmdMat.m_ambient.r,mmdMat.m_ambient.g,mmdMat.m_ambient.b));
        program.setUniformValue(scene->shader->m_uDiffuse,QVector3D(mmdMat.m_diffuse.r,mmdMat.m_diffuse.g,mmdMat.m_diffuse.b));
        program.setUniformValue(scene->shader->m_uSpecular,QVector3D(mmdMat.m_specular.r,mmdMat.m_specular.g,mmdMat.m_specular.b));
        program.setUniformValue(scene->shader->m_uSpecularPower,mmdMat.m_specularPower);
        program.setUniformValue(scene->shader->m_uAlpha,mmdMat.m_alpha);

        glActiveTexture(GL_TEXTURE0 + 0);
        program.setUniformValue(scene->shader->m_uTex,0);
        if (mat.m_texture != 0)
        {
            if (mat.m_texture->format() != QOpenGLTexture::RGBA8_UNorm)
            {
                // Use Material Alpha
                program.setUniformValue(scene->shader->m_uTexMode,1);
            }
            else
            {
                // Use Material Alpha * Texture Alpha
                program.setUniformValue(scene->shader->m_uTexMode,2);
            }
            program.setUniformValue(scene->shader->m_uTexMulFactor,QVector4D(mmdMat.m_textureMulFactor.r,mmdMat.m_textureMulFactor.g,mmdMat.m_textureMulFactor.b,mmdMat.m_textureMulFactor.a));
            program.setUniformValue(scene->shader->m_uTexAddFactor,QVector4D(mmdMat.m_textureAddFactor.r,mmdMat.m_textureAddFactor.g,mmdMat.m_textureAddFactor.b,mmdMat.m_textureAddFactor.a));
            mat.m_texture->bind();
        }
        else
        {
            program.setUniformValue(scene->shader->m_uTexMode,0);
            glBindTexture(GL_TEXTURE_2D, m_dummyColorTex);
        }

        glActiveTexture(GL_TEXTURE0 + 1);
        program.setUniformValue(scene->shader->m_uSphereTex,1);
        if (mat.m_spTexture->isCreated())
        {
            if (mmdMat.m_spTextureMode == saba::MMDMaterial::SphereTextureMode::Mul)
            {
                program.setUniformValue(scene->shader->m_uSphereTexMode,1);
            }
            else if (mmdMat.m_spTextureMode == saba::MMDMaterial::SphereTextureMode::Add)
            {
                program.setUniformValue(scene->shader->m_uSphereTexMode, 2);
            }
            program.setUniformValue(scene->shader->m_uSphereTexMulFactor,QVector4D(mmdMat.m_spTextureMulFactor.r,mmdMat.m_spTextureMulFactor.g,mmdMat.m_spTextureMulFactor.b,mmdMat.m_spTextureMulFactor.a));
            program.setUniformValue(scene->shader->m_uSphereTexAddFactor,QVector4D(mmdMat.m_spTextureAddFactor.r,mmdMat.m_spTextureAddFactor.g,mmdMat.m_spTextureAddFactor.b,mmdMat.m_spTextureAddFactor.a));
            mat.m_spTexture->bind();
        }
        else
        {
            program.setUniformValue(scene->shader->m_uSphereTexMode, 0);
            glBindTexture(GL_TEXTURE_2D, m_dummyColorTex);
        }

        glActiveTexture(GL_TEXTURE0 + 2);
        program.setUniformValue(scene->shader->m_uToonTex, 2);
        if (mat.m_toonTexture->isCreated())
        {
            program.setUniformValue(scene->shader->m_uToonTexMulFactor,QVector4D(mmdMat.m_toonTextureMulFactor.r,mmdMat.m_toonTextureMulFactor.g,mmdMat.m_toonTextureMulFactor.b,mmdMat.m_toonTextureMulFactor.a));
            program.setUniformValue(scene->shader->m_uToonTexAddFactor,QVector4D(mmdMat.m_toonTextureAddFactor.r,mmdMat.m_toonTextureAddFactor.g,mmdMat.m_toonTextureAddFactor.b,mmdMat.m_toonTextureAddFactor.a));
            program.setUniformValue(scene->shader->m_uToonTexMode, 1);
            mat.m_toonTexture->bind();
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        }
        else
        {
            program.setUniformValue(scene->shader->m_uToonTexMode, 0);
            glBindTexture(GL_TEXTURE_2D, m_dummyColorTex);
        }

        glm::vec3 lightColor = scene->m_lightColor;
        glm::vec3 lightDir = scene->m_lightDir;
        glm::mat3 viewMat = glm::mat3(scene->m_viewMat);
        lightDir = viewMat * lightDir;
        program.setUniformValue(scene->shader->m_uLightDir,lightDir.x,lightDir.y,lightDir.z);
        program.setUniformValue(scene->shader->m_uLightColor,lightColor.x,lightColor.y,lightColor.z);

        if (mmdMat.m_bothFace)
        {
            glDisable(GL_CULL_FACE);
        }
        else
        {
            glEnable(GL_CULL_FACE);
            glCullFace(GL_BACK);
        }

        if (alphaBlend)
        {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        }
        else
        {
            glDisable(GL_BLEND);
        }

        size_t offset = subMesh.m_beginIndex * model->GetIndexElementSize();
        glDrawElements(GL_TRIANGLES, subMesh.m_vertexCount, m_indexType, (GLvoid*)offset);

        glActiveTexture(GL_TEXTURE0 + 2);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE0 + 0);
        glBindTexture(GL_TEXTURE_2D, 0);
    }

    glActiveTexture(GL_TEXTURE0 + 3);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0 + 4);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0 + 5);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0 + 6);
    glBindTexture(GL_TEXTURE_2D, 0);

    glDisable(GL_POLYGON_OFFSET_FILL);
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_BLEND);
}

void Model::Update(){
    frames++;

    AutoBlink();
    AutoMonth();
    int tmp = frames;
    if(Anim_Loop && anim->GetMaxKeyTime()<=tmp)tmp%=anim->GetMaxKeyTime();
    model->BeginAnimation();
    model->UpdateAllAnimation(anim,tmp,0.03f);
    model->EndAnimation();

    model->Update();
    size_t vtxCount = model->GetVertexCount();
    m_posVBO.bind();
    m_posVBO.allocate(model->GetUpdatePositions(),sizeof(glm::vec3) * vtxCount);
    m_norVBO.bind();
    m_norVBO.allocate(model->GetUpdateNormals(),sizeof(glm::vec3) * vtxCount);
    m_uvVBO.bind();
    m_uvVBO.allocate(model->GetUpdateUVs(),sizeof(glm::vec2) * vtxCount);
}

void Model::InitMorph()
{
    auto morphMan = model->GetMorphManager();
    month = morphMan->GetMorph(3);
    eye = morphMan->GetMorph(16);
}

void Model::AutoMonth()
{
    if(month_enable){
        month->SetWeight(0.5f*(sin(frames*month_speed)+1.0f));
    }else {
        month->SetWeight(0.0f);
    }
}

void Model::AutoBlink()
{
    if(Blink_enable){
        eye->SetWeight(0.5f*(sin(frames*Blink_speed)+1.0f));
        Blink_time += 0.03f;
        if(Blink_time > Blink_last){
            Blink_time = 0.0f;
            Blink_enable = false;
            eye->SetWeight(0.0f);
        }
    }else{
        Blink_time += 0.03f;
        if(Blink_time > Blink_interval){
            Blink_time = 0.0f;
            Blink_enable = true;
        }
    }
}
